package gek.uni.diplom.ocl.example.utils;

import gek.uni.diplom.ocl.example.exceptions.ErrorException;
import gek.uni.diplom.ocl.example.patterns.hierarhical.AChangeableCodeNode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.PropertyCallExp;

public class GeneratorUtils {

  public static String REPLACE_CODE_STRING = "@INSERT_CODE";
  public static String TAB_CHAR = "  ";

  private static Map<String, Long> prefix2count = new HashMap<String, Long>();

  public static String getUniqueName(String prefix) {
    Long cnt = prefix2count.get(prefix);

    if (cnt == null) {
      cnt = new Long(0);
    }
    String result = prefix + "_" + String.valueOf(cnt);
    prefix2count.put(prefix, cnt + 1);
    return result;
  }

  /**
   * Первый элемент результата - код определения переменной, второй и далее
   * элементы - строки импорта пакетов, необходимых для работы с переменной
   * 
   * @param clazz
   * @param varName
   * @return
   * @throws ErrorException
   */
  public static String[] createVariableDeclarationString(EClassifier clazz,
      String varName) {

    // String[] result = new String[2];

    String declarationString = "";
    StringBuilder stringBldr = new StringBuilder();
    ArrayList<String> importedStrings = new ArrayList<String>();
    importedStrings.add("import " + clazz.getInstanceClassName() + ";");

    // StackFactory.eINSTANCE.create
    EFactory factory = clazz.getEPackage().getEFactoryInstance();

    String factoryName = factory.getClass().getSimpleName();
    for (Class<?> iface : factory.getClass().getInterfaces()) {
      if (factory.getClass().getPackage().getName().contains(
          iface.getPackage().getName())
          && factoryName.contains(iface.getSimpleName())) {

        String importString = "import " + iface.getName() + ";";
        importedStrings.add(importString);
        // ptrn.addImportSection(varName, iface.getName());
        factoryName = iface.getSimpleName();
        break;
      }
    }

    // int index = factoryName.lastIndexOf("Impl");
    // factoryName = factoryName.substring(0, index);
    stringBldr.append(clazz.getName()).append(" ").append(varName).append(";");
    // .append(" = ").append(factoryName).append(".").append("create").append(
    // clazz.getName()).append(";");
    declarationString = stringBldr.toString();

    stringBldr = new StringBuilder();
    stringBldr.append(varName).append(" = ").append(factoryName).append(
        ".eINSTANCE").append(".").append("create").append(clazz.getName())
        .append("();");
    String initializationString = stringBldr.toString();

    // clazz.getName()).append(";");
    String[] result = new String[importedStrings.size() + 2];
    result[0] = declarationString;
    result[1] = initializationString;
    int offSet = 2;
    for (int i = 0; i < importedStrings.size(); i++) {
      result[i + offSet] = importedStrings.get(i);
    }
    return result;
  }

  public static String createImportStringForClassifier(EClassifier clazz) {
    return "import " + clazz.getName() + ";";
  }

  /**
   * создает и возвращает массив из двух строк: в первой - код, инициализирующий
   * работу get-метода, во второй - код get-метода
   * 
   * @param varName
   * @param ptrn
   * @return
   */
  public static String[] createVariableGetterCode(String varName) {

    String result[] = new String[2];

    StringBuilder getterInitString = new StringBuilder("");

    StringBuilder getterCallString = new StringBuilder(varName);
    result[0] = getterInitString.toString();
    result[1] = getterCallString.toString();
    return result;
  }

  /**
   * создает и возвращает массив из двух или более строк: в первой - код,
   * определяющий переменную, во второй - код модификатора с выражением для
   * подстановки реального выражения, в третьей и далее строки, описывающие
   * импортируемые классы
   * 
   * @param varName
   * @param clazz
   *          TODO
   * @param ptrn
   * @return
   */
  public static String[] createVariableSetterStrings(String varName,
      EClassifier clazz) {

    List<String> importedStrings = new ArrayList<String>();

    StringBuilder setterInitString = new StringBuilder();

    EFactory factory = clazz.getEPackage().getEFactoryInstance();

    String factoryName = factory.getClass().getSimpleName();
    for (Class<?> iface : factory.getClass().getInterfaces()) {
      if (factory.getClass().getPackage().getName().contains(
          iface.getPackage().getName())
          && factoryName.contains(iface.getSimpleName())) {

        String importString = "import " + iface.getName() + ";";
        importedStrings.add(importString);
        // ptrn.addImportString(importString);
        factoryName = iface.getSimpleName();
        break;
      }
    }

    setterInitString.append(clazz.getName()).append(" ").append(varName)
        ./* append(";").append(varName). */append(" = ").append(factoryName)
        .append(".eINSTANCE").append(".").append("create").append(
            clazz.getName()).append("();");

    StringBuilder setterCallString =
        new StringBuilder().append(varName).append(" = ").append(
            REPLACE_CODE_STRING).append(";");

    String[] result = new String[importedStrings.size() + 2];
    result[0] = setterInitString.toString();
    result[1] = setterCallString.toString();
    int k = 2;
    for (int i = 0; i < importedStrings.size(); i++) {
      result[i + k] = importedStrings.get(i);
    }
    return result;
  }

  /**
   * Генерирует 3 и более строк и возвращает массив, содержащий их.<br/>
   * В первой строке содержится имя свойства, по которому будем обращаться в
   * коде, во второй строке - код, иницилизирующий переменные, необходимые для
   * корректной работы get-метода<br/>
   * В третьей строке содержится код, вызывающий get-метод.<br/>
   * В третьей и далее строках содержатся строки, описывающие импортируемые
   * классы<br/>
   * <br/>
   * Пример генерируемого кода:<br/>
   * 0:<br/>
   * EClass classifier = (EClass)<br/>
   * StackPackage.eINSTANCE.getEClassifier("Stack"); <br/>
   * EStructuralFeature feature = classifier.getEStructuralFeature("children");<br/>
   * Stack stack = null;<br/>
   * <br/>
   * 1:<br/>
   * stack.eGet(feature);
   * 
   * @param source
   *          - объект, get-метод которого генерируется
   * @param property
   *          - свойство, которое возвращается с помощью вызова get-метода
   * @param pattern
   *          - накапливающий шаблон
   * @return массив из сгенерированных строк
   */
  public static String[] generateProperyGetterStrings(
      OCLExpression<EClassifier> source, EStructuralFeature property,
      final AChangeableCodeNode pattern) {

    List<String> importedStrings = new ArrayList<String>();

    EClassifier sourceClassifier = source.getType();
    String reference = pattern.getGetterCallString();// pattern.getReference(source.toString());

    importedStrings.add(new String("import ")
        + sourceClassifier.getEPackage().getClass().getName() + ";");
    importedStrings.add(new String("import ")
        + sourceClassifier.getInstanceClassName() + ";");
    importedStrings.add(new String("import "
        + EStructuralFeature.class.getName())
        + ";");

    String classifierName = GeneratorUtils.getUniqueName("classifier");
    String featureName = GeneratorUtils.getUniqueName("feature");
    String packageName = sourceClassifier.getEPackage().getName() + "Package";
    String className = sourceClassifier.getName();
    String propertyName = property.getName();

    // Генерируем строку для инициализации геттера
    StringBuilder getterInitString = new StringBuilder();
    getterInitString.append("EClass ").append(classifierName).append(" = ")
        .append("(EClass)").append(packageName).append(".eINSTANCE").append(
            ".getEClassifier(").append("\"").append(className).append("\")")
        .append(";").append("\n");

    getterInitString.append("EStructuralFeature ").append(featureName).append(
        " = ").append(classifierName).append(".").append(
        "getEStructuralFeature(\"").append(propertyName).append("\")").append(
        ";\n");

    // Генерируем строку для обращения к свойству. Вместе с кастованием в
    // правильный тип
    StringBuilder getterReferenceString =
        new StringBuilder().append("((").append(className).append(")").append(
            reference).append(".eGet(").append(featureName).append("))");

    String[] result = new String[importedStrings.size() + 3];
    result[0] = featureName;
    result[1] = getterInitString.toString();
    result[2] = getterReferenceString.toString();
    int k = 3;
    for (int i = 0; i < importedStrings.size(); i++) {
      result[i + k] = importedStrings.get(i);
    }
    return result;

    // ========= declaration: Пример кода
    /*
     * EClass classifier = (EClass)
     * StackPackage.eINSTANCE.getEClassifier("Stack"); EStructuralFeature
     * feature = classifier.getEStructuralFeature("children"); Stack stack =
     * null; stack.eGet(feature);
     * 
     * feature.setEType(StackPackage.eINSTANCE.getStack());
     * feature.setName("stack"); StackFactory.eINSTANCE.eGet(feature);
     */// --------- declaration
  }

  /**
   * Генерирует 2 строки и возвращает массив, содержащий их.<br/>
   * В первой строке содержится код, иницилизирующий переменные, необходимые для
   * корректной работы set-метода<br/>
   * Во второй строке содержится код, вызывающий set-метод.<br/>
   * <br/>
   * Пример генерируемого кода:<br/>
   * 0:<br/>
   * EClass classifier = (EClass)<br/>
   * StackPackage.eINSTANCE.getEClassifier("Stack"); <br/>
   * EStructuralFeature feature = classifier.getEStructuralFeature("children");<br/>
   * Stack stack = null;<br/>
   * <br/>
   * 1:<br/>
   * stack.eSet(feature, @INSERT_CODE);
   * 
   * @param source
   *          - объект, get-метод которого генерируется
   * @param property
   *          - свойство, которое возвращается с помощью вызова get-метода
   * @param pattern
   *          - накапливающий шаблон
   * @return массив из сгенерированных строк
   */
  public static String[] generatePropertySetterStrings(
      PropertyCallExp<EClassifier, EStructuralFeature> callExp,
      EStructuralFeature property, final AChangeableCodeNode pattern,
      String featureName) {

    OCLExpression<EClassifier> source = callExp.getSource();

    EClassifier sourceClassifier = source.getType();
    String reference = pattern.getGetterCallString();// pattern.getReference(source.toString());

    List<String> importedStrings = new ArrayList<String>();
    importedStrings.add(new String("import ")
        + sourceClassifier.getEPackage().getClass().getName() + ";");
    importedStrings.add(new String("import ")
        + sourceClassifier.getInstanceClassName() + ";");
    importedStrings.add(new String("import "
        + EStructuralFeature.class.getName())
        + ";");

    String classifierName = GeneratorUtils.getUniqueName("classifier");
    featureName =
        featureName != null ? featureName : GeneratorUtils
            .getUniqueName("feature");
    String packageName = sourceClassifier.getEPackage().getName() + "Package";
    String className = sourceClassifier.getName();
    String propertyName = property.getName();

    // Генерируем строку для инициализации геттера
    StringBuilder setterInitString = null;
    try {
      setterInitString = new StringBuilder(pattern.getGetterInitString());
    } catch (NullPointerException e) {
      setterInitString = new StringBuilder("");
      setterInitString.append("EClass ").append(classifierName).append(" = ")
          .append("(EClass)").append(packageName).append(".eINSTANCE").append(
              ".getEClassifier(").append("\"").append(className).append("\")")
          .append(";").append("\n");

      setterInitString.append("EStructuralFeature ").append(featureName)
          .append(" = ").append(classifierName).append(".").append(
              "getEStructuralFeature(\"").append(propertyName).append("\")")
          .append(";\n");
    }

    // Генерируем строку для обращения к свойству
    StringBuilder setterReferenceString =
        new StringBuilder().append(reference).append(".eSet(").append(
            featureName).append(", ").append(REPLACE_CODE_STRING).append(");");

    String[] result = new String[importedStrings.size() + 2];
    result[0] = setterInitString.toString();
    result[1] = setterReferenceString.toString();
    int k = 2;
    for (int i = 0; i < importedStrings.size(); i++) {
      result[i + k] = importedStrings.get(i);
    }
    return result;

  }

  public static String createNonNullObjectInitializationString(EClassifier clazz)
      throws ErrorException {
    StringBuilder result = new StringBuilder();

    String clazzSimpleName = clazz.getName();

    // StackFactory.eINSTANCE.create
    EFactory factory = clazz.getEPackage().getEFactoryInstance();

    Boolean notFound = true;
    String factoryName = factory.getClass().getSimpleName();
    for (Class<?> iface : factory.getClass().getInterfaces()) {
      if (factory.getClass().getPackage().getName().contains(
          iface.getPackage().getName())
          && factoryName.contains(iface.getSimpleName())) {
        factoryName = iface.getSimpleName();
        notFound = false;
        break;
      }
    }
    if (notFound)
      throw new ErrorException("Не удается определить фабрику объектов типа "
          + clazz.getInstanceClassName());

    result.append(factoryName).append(".eINSTANCE.create").append(
        clazzSimpleName).append("()");

    return result.toString();
  }

  public static String[] createAddElementsToCollection(String collectionGetterCode,
      EClassifier elementType) throws ErrorException {
    String varName = getUniqueName("temp");

    String[] variableDeclarationString =
        createVariableDeclarationString(elementType, varName);

    String[] variableGetterCode = createVariableGetterCode(varName);

    String[] variableSetterStrings =
        createVariableSetterStrings(varName, elementType);

    String initializationString =
        createNonNullObjectInitializationString(elementType);

    StringBuilder result = new StringBuilder();

    String initVarString =
        variableSetterStrings[1].replaceFirst(
            GeneratorUtils.REPLACE_CODE_STRING, initializationString);

    result.append("for (int i = 0; i< ");
    result.append(GeneratorUtils.REPLACE_CODE_STRING);
    result.append("; i++){\n");

    result.append(TAB_CHAR).append(variableDeclarationString[0]).append("\n");
    result.append(TAB_CHAR).append(initVarString).append("\n");
//    result.append("\t").append(variableGetterCode[1]).append("\n");
    result.append(TAB_CHAR).append(collectionGetterCode).append(".add(").append(
        variableGetterCode[1]).append(");\n");

    result.append("}");

    int resultStringsLen =
        variableDeclarationString.length - 2 + variableSetterStrings.length - 1;
    String resultStrings[] = new String[resultStringsLen];
    resultStrings[0] = result.toString();
    
    int total = 1;
    for (int i = 2; i< variableDeclarationString.length  ; i++){
      resultStrings[total+i-2] = variableDeclarationString[i];
    }
    total+= variableDeclarationString.length-2;
   
    for (int i = 2; i< variableSetterStrings.length ; i++, total++){
      resultStrings[total+i-2] = variableSetterStrings[i];
    }
    total += variableSetterStrings.length-2;
    return resultStrings;
  }

  public static String createMethodCallCode(String getterString,
      String methodName, List<Object> argList) {
    StringBuilder resultBldr = new StringBuilder();
    resultBldr.append(getterString).append(".").append(methodName).append("(");
    if (argList != null && !argList.isEmpty()){
      //TODO генерация кода для вызова метода с параметрами
    }
    resultBldr.append(")");
    return resultBldr.toString();
  }
}
